On this page

Skip to content

How to Customize Default Model Validation Error Messages in ASP.NET Core

TLDR

  • ASP.NET Core's default Model Validation messages are provided in English only; they can be customized by creating resource files (.resx).
  • Validation messages are divided into "ModelBinding" and "ValidationMetadata" categories, which must be implemented separately.
  • By implementing the IValidationMetadataProvider interface, you can automatically replace the default error messages of ValidationAttribute.
  • When supporting multiple languages, set the resource file to "No code generation" and configure SupportedUICultures via RequestLocalizationOptions.
  • When configuring locales, distinguish between Culture (formatting) and UICulture (resource loading) to avoid confusion.

Customizing Default Model Validation Messages

When you encounter this issue: When a project requires converting built-in ASP.NET Core validation error messages (such as "The field is required." generated by RequiredAttribute) into Chinese or other languages, and you do not want to manually set error messages for every field.

ASP.NET Core's validation mechanism is divided into two categories:

  • ModelBinding Validation: Related to data format (e.g., type conversion failure).
  • ValidationMetadata Validation: Related to data content rules (e.g., length limits, required fields).

Creating Resource Files (.resx)

First, create a resource file to store the corresponding error messages. Set the resource file properties as follows:

  • Build Action: Embedded Resource
  • Copy to Output Directory: Do not copy

Enter the required error message key-value pairs into the resource file, for example, RequiredAttribute_ValidationError mapped to "{0} field is required.".

Creating a Custom ValidationMetadataProvider

To automatically replace the error messages of ValidationAttribute, you need to implement IValidationMetadataProvider.

csharp
public class LocalizationValidationMetadataProvider : IValidationMetadataProvider {
    private readonly ResourceManager resourceManager;
    private readonly Type resourceType;

    public LocalizationValidationMetadataProvider(Type type) {
        resourceType = type;
        resourceManager = new ResourceManager(type);
    }

    public void CreateValidationMetadata(ValidationMetadataProviderContext context) {
        foreach (var attribute in context.ValidationMetadata.ValidatorMetadata.OfType<ValidationAttribute>()) {
            if (attribute.ErrorMessageResourceName is null) {
                bool hasErrorMessage = attribute.ErrorMessage != null;

                if (hasErrorMessage) {
                    string? defaultErrorMessage = typeof(ValidationAttribute)
                        .GetField("_defaultErrorMessage", BindingFlags.NonPublic | BindingFlags.Instance)
                        ?.GetValue(attribute) as string;

                    hasErrorMessage = attribute.ErrorMessage != defaultErrorMessage;
                }

                if (hasErrorMessage) {
                    continue;
                }

                string? name = GetMessageName(attribute);
                if (name != null && resourceManager.GetString(name) != null) {
                    attribute.ErrorMessageResourceType = resourceType;
                    attribute.ErrorMessageResourceName = name;
                    attribute.ErrorMessage = null;
                }
            }
        }
    }

    private string? GetMessageName(ValidationAttribute attr) {
        switch (attr) {
            case CompareAttribute _:
                return "CompareAttribute_MustMatch";
            case StringLengthAttribute vAttr:
                if (vAttr.MinimumLength > 0) {
                    return "StringLengthAttribute_ValidationErrorIncludingMinimum";
                }
                return "StringLengthAttribute_ValidationError";
            case DataTypeAttribute _:
                return $"{attr.GetType().Name}_Invalid";
            case ValidationAttribute _:
                return $"{attr.GetType().Name}_ValidationError";
        }

        return null;
    }
}

Registering Services

Register the provider mentioned above in Program.cs and configure the ModelBindingMessageProvider.

csharp
builder.Services.AddRazorPages()
    .AddMvcOptions(options => {
        var provider = options.ModelBindingMessageProvider;
        provider.SetAttemptedValueIsInvalidAccessor((x, y) => string.Format(ModelBindingMessage.AttemptedValueIsInvalid, x, y));
        // Other ModelBinding message settings...
        
        options.ModelMetadataDetailsProviders.Add(new LocalizationValidationMetadataProvider(typeof(ValidationMetadataMessage)));
    });

Multi-language Support

When you encounter this issue: When the application needs to dynamically switch the language of validation error messages based on the user's locale settings.

Configuring Language Resource Files

Create resource files for the corresponding languages (e.g., ValidationMetadataMessage.zh-TW.resx) and set their access modifier to "No code generation". The system will automatically read the corresponding resource file based on the UICulture.

Configuring RequestLocalization

Configure RequestLocalizationOptions in Program.cs.

csharp
WebApplication app = builder.Build();

string[] supportedCultures = new string[] { "zh-TW", "en-US" };
RequestLocalizationOptions localizationOptions = new RequestLocalizationOptions()
    .SetDefaultCulture(supportedCultures[0])
    .AddSupportedCultures(supportedCultures)
    .AddSupportedUICultures(supportedCultures);

app.UseRequestLocalization(localizationOptions);

INFO

Common Misconceptions about Culture

  1. How DefaultRequestCulture works: It is the provider with the lowest priority. The system will attempt to find the UICulture from the QueryString, Cookie, or Accept-Language Header in order; it will only use DefaultRequestCulture if none are found.
  2. Difference between Culture and UICulture:
    • Culture: Controls the formatting and sorting of dates, numbers, and currency.
    • UICulture: Controls which language resource file is loaded.
    • If setting the language via QueryString, the correct parameter name should be ui-culture rather than culture.

Change Log

  • 2022-10-05 Initial document creation.
  • 2024-04-04 Fixed ModelBindingMessage messages.